package code.wrtp;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import code.exceptions.BadPduFormatException;
import code.network.AddressUtil;
import code.network.PacketRx;
import code.wrtp.packets.Packet;
import code.wrtp.packets.SolicitEntering;
import code.wrtp.packets.SolicitEnteringReply;

public class Receiver implements Runnable {

	private PacketRx packetRx;
	private Sender sender;
	private Station station; /* Parent */
	private volatile boolean isRunning;
	private Thread receiverThread;
	private Queue<Message> pduQueue;
	private final short broadcastID;
	private final Node node;

	public Receiver(Sender sender, Station station, String subNet, short port,
			short address) {

		this.station = station;
		this.sender = sender;
		node = station.getNode();
		broadcastID = Constants.BROADCAST_ADDRESS;
		packetRx = new PacketRx(this, subNet, address, port);
		pduQueue = new ConcurrentLinkedQueue<Message>();
	}

	public void startThread() {

		isRunning = true;
		packetRx.startThread();
		receiverThread = new Thread(this);
		receiverThread.start();
	}

	public void stopThread() {
		isRunning = false;
		packetRx.stopThread();
		receiverThread.interrupt();
	}

	@Override
	public void run() {
		while (isRunning) {
			try {

				Message messagePacketReceived = pduQueue.poll();

				if (messagePacketReceived != null) {
					byte frameControl = messagePacketReceived
							.getFrameControlType();
					switch (frameControl) {

					case FrameControlType.SOLICIT_ENTERING:
					case FrameControlType.VARAL_SOLICIT_ENTERING:
						if ((station.getNode().getState() == State.HAVING)
								|| ((station.getNode().getState() == State.RESET) && (station
										.getNode().getPreviousState() == State.SOLICITING))) {/*
																							 * Having
																							 * token
																							 * OR
																							 * selfRing
																							 */
							SolicitEntering solicitEnteringPacket = new SolicitEntering();
							solicitEnteringPacket
									.ParseBytes(messagePacketReceived.getData());
							station.replyToSolicitEntering(
									solicitEnteringPacket.getSA(), frameControl);
						}
						break;
					case FrameControlType.RING_ENTERING_REPLY:
					case FrameControlType.VARAL_ENTERING_REPLY:
						SolicitEnteringReply solicitEnteringReply = new SolicitEnteringReply();
						solicitEnteringReply.ParseBytes(messagePacketReceived
								.getData());
						station.addSolicitEnteringReplyPacket(
								solicitEnteringReply.getFC(),
								solicitEnteringReply.getDA(),
								solicitEnteringReply.getSA(),
								solicitEnteringReply.getMySuccessorID());
						break;
					default:
						break;
					}
				}
			} catch (BadPduFormatException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	private class Message {

		private short nodeSender;
		private byte[] data;

		public Message(short nodeSender, byte[] data) {
			this.nodeSender = nodeSender;
			this.data = data;
		}

		public short getNodeSender() {
			return nodeSender;
		}

		public byte[] getData() {
			return data;
		}

		public byte getFrameControlType() {
			String[] splittedString = new String(data).split(";", 2);
			if (splittedString.length == 2) {
				return Byte.parseByte(splittedString[0]);
			} else
				throw new NumberFormatException();
		}
	}

	public void keepReceivedMessage(short nodeSenderAddress, byte[] data) {
		if (nodeSenderAddress != node.getNodeID()) { /*
													 * We don't need enqueue
													 * packets from this node
													 */
			pduQueue.add(new Message(nodeSenderAddress, data));
			synchronized (pduQueue) {
				pduQueue.notify();
			}
		}
	}

	public AddressUtil getAddressUtil() {
		return station.getAddressUtil();
	}

	public PacketRx getPacketRx() {
		return packetRx;
	}

	public Sender getSender() {
		return sender;
	}

	public Station getStation() {
		return station;
	}

	public boolean isRunning() {
		return isRunning;
	}

	public Thread getReceiverThread() {
		return receiverThread;
	}

	public Queue<Message> getPduQueue() {
		return pduQueue;
	}
}
